package be.android.forap.javacard;

import javax.crypto.Cipher;
import javax.crypto.CipherOutputStream;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.Buffer;

import javax.crypto.NullCipher;

import android.util.Log;

public class CipherOutputStreamToSE extends CipherOutputStream {

	private static final String TAG = "CipherOutputStreamToSE";

	private boolean javacardDetected= false;

	//private final Cipher cipher;
	private final byte[] arr = new byte[1];

	private ByteArrayOutputStream buf;
	/***
	 * Creates a new {@code CipherOutputStream} instance for an {@code
	 * OutputStream} and a {@code Cipher}.
	 *
	 * @param os
	 *            the output stream to write data to.
	 * @param c
	 *            the cipher to process the data with.
	 * @throws IOException 
	 */
	public CipherOutputStreamToSE(OutputStream os, Cipher c) throws IOException {
		super(os);
		Log.d("JC","Using Java Card for AES");
		//cipher = c;
		startTime = System.currentTimeMillis();
		SecureElement.initDataEncryption();
		//Log.i("IV",CryptoUtil.toHex(iv));
		//out.write(iv);
		buf = new ByteArrayOutputStream();
		
		stopTime = System.currentTimeMillis();
		long elapsedTime = stopTime - startTime;
		Log.w("TIME init", "tijd = "+ elapsedTime);
	}

	long startTime;
	long stopTime;

	/***
	 * Creates a new {@code CipherOutputStream} instance for an {@code
	 * OutputStream} without a cipher.
	 * <p>
	 * A {@code NullCipher} is created to process the data.
	 *
	 * @param os
	 *            the output stream to write the data to.
	 * @throws IOException 
	 */
	protected CipherOutputStreamToSE(OutputStream os) throws IOException {
		this(os, new NullCipher());
	}

	/***
	 * Writes the single byte to this cipher output stream.
	 *
	 * @param b
	 *            the byte to write.
	 * @throws IOException
	 *             if an error occurs.
	 */
	@Override
	public void write(int b) throws IOException {
		Log.e(TAG,"UPDATE 1 byte");
		startTime = System.currentTimeMillis();
		byte[] result;
		arr[0] = (byte) b;
		if(buf.size()>=512){
			//result = SecureElement.updateDataEncryption(arr);//result = cipher.update(arr);
			result = SecureElement.updateDataEncryption(buf.toByteArray());//result = cipher.update(arr);
			if (result != null) {
				out.write(result);
			}
			buf.reset();
		}else{
			buf.write(  b);
		}

		stopTime = System.currentTimeMillis();
		long elapsedTime = stopTime - startTime;
		Log.w("TIME 1 byte", "tijd = "+ elapsedTime);
	}

	/***
	 * Writes the buffer of bytes to this cipher output stream.
	 *
	 * @param b
	 *            the buffer of bytes.
	 * @throws IOException
	 *             if an error occurs.
	 */
	@Override
	public void write(byte[] b) throws IOException {
		startTime = System.currentTimeMillis();
		write(b, 0, b.length);
		stopTime = System.currentTimeMillis();
		long elapsedTime = stopTime - startTime;
		Log.w("TIME byte", "tijd = "+ elapsedTime);
	}

	/***
	 * Writes the {@code len} bytes from buffer {@code b} starting at offset
	 * {@code off} to this cipher output stream.
	 *
	 * @param b
	 *            the buffer.
	 * @param off
	 *            the offset to start at.
	 * @param len
	 *            the number of bytes.
	 * @throws IOException
	 *             if an error occurs.
	 */
	@Override
	public void write(byte[] b, int off, int len) throws IOException {
		Log.e(TAG,"UPDATE");
		startTime = System.currentTimeMillis();
		if (len == 0) {
			return;
		}
		if(len+buf.size()>=512){
			buf.write(b);
			byte[] result = SecureElement.updateDataEncryption(buf.toByteArray());
			//byte[] result = SecureElement.updateDataEncryption(b);//cipher.update(b, off, len);
			if (result != null) {
				out.write(result);
			}
			buf.reset();
		}else{
			buf.write(b);
		}
		stopTime = System.currentTimeMillis();
		long elapsedTime = stopTime - startTime;
		Log.w("TIME byte[]", "tijd = "+ elapsedTime);
	}

	public void fin() throws IOException {
		Log.e(TAG,"Fin");
		if (buf.size() == 0) {
			return;
		}


		byte[] result = SecureElement.updateDataEncryption(buf.toByteArray());
		//byte[] result = SecureElement.updateDataEncryption(b);//cipher.update(b, off, len);
		if (result != null) {
			out.write(result);
		}
		buf.close();
		stopTime = System.currentTimeMillis();
		long elapsedTime = stopTime - startTime;
		Log.w("TIME byte[]", "tijd = "+ elapsedTime);
	}

	/***
	 * Flushes this cipher output stream.
	 *
	 * @throws IOException
	 *             if an error occurs
	 */
	@Override
	public void flush() throws IOException {
		out.flush();
	}

	/***
	 * Close this cipher output stream.
	 * <p>
	 * On the underlying cipher {@code doFinal} will be invoked, and any
	 * buffered bytes from the cipher are also written out, and the cipher is
	 * reset to its initial state. The underlying output stream is also closed.
	 *
	 * @throws IOException
	 *             if an error occurs.
	 */
	@Override
	public void close() throws IOException {
		Log.e(TAG,"FINALIZE");
		byte[] result;
		try {

			/*if (cipher != null) {
				result = cipher.doFinal();
				if (result != null) {
					out.write(result);
				}
			}*/
			result = SecureElement.doFinalDataEncryption();
			if (result != null) {
				out.write(result);
			}
			if (out != null) {
				out.flush();
			}
		} finally {
			if (out != null) {
				out.close();
			}
		}
	}
}
